﻿//_______________________________________________________________________________________________________________________
// Copy Right (c) 2010, 2011 SapiensIT Consulting GmbH, all rights reserved
//_______________________________________________________________________________________________________________________
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//_______________________________________________________________________________________________________________________
// PROJECT:     sapiens.at.SharePoint (SharePoint 2010/V4)
//_______________________________________________________________________________________________________________________
// VERSION:     2.0
//_______________________________________________________________________________________________________________________
// DESCRIPTION: 
//_______________________________________________________________________________________________________________________
// LICENSE:     Different licens Types apply to different file types:
//               - Source code files are governed by the MICROSOFT PUBLIC LICENSE (Ms-PL)
//               - Binary files are governed by MSDN CODE GALLERY BINARY LICENSE
//               - Documentation files are governed by CREATIVE COMMONS ATTRIBUTION 3.0 LICENSE
//
//               The licenses can be found at the projects source location.
//               If you do not agree, don't use it!
// 
//_______________________________________________________________________________________________________________________
// Start SharePoint Branding, visit http://www.cliggin.com! Visit us on http://www.sapiens.at! Tell your friends! 
//_______________________________________________________________________________________________________________________
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.SharePoint;
using Sapiens.at.SharePoint.Extensions;
using System.IO;

namespace Sapiens.at.SharePoint.Entities
{
    /// <summary>
    /// Maps listitems to the right entities.
    /// </summary>
    public static class ListItemMapper
    {
        /// <summary>
        /// Gets an entity out of event properties.
        /// </summary>
        /// <remarks>
        /// Use this method to get an entity inside of an event receiver. The separate web argument is to provide a way to pass a privileged web.
        /// </remarks>
        /// <param name="eventProperties">The eventproperties for which the entity is instantiated</param>
        /// <param name="web">The web. If null the web of the eventProperties is taken.</param>
        /// <param name="entityAssembly">The assembly where the entities are defined</param>
        /// <returns></returns>
        public static IEntity GetEntity(SPItemEventProperties eventProperties, SPWeb web, Assembly entityAssembly)
        {
            return (IEntity)Activator.CreateInstance(GetEntityType(eventProperties, web ?? eventProperties.Web, entityAssembly), new object[] { eventProperties, web });
        }

        /// <summary>
        /// Gets an entity for a listitem.
        /// </summary>
        /// <param name="eventProperties">The current eventproperties</param>
        /// <param name="listItem">The listitem that the new entity wraps</param>
        /// <param name="web">The web. If null the web of the listitem is taken.</param>
        /// <param name="entityAssembly">The assembly where the entities are defined</param>
        /// <returns></returns>
        public static IEntity GetEntity(SPItemEventProperties eventProperties, SPListItem listItem, SPWeb web, Assembly entityAssembly)
        {
            return (IEntity)Activator.CreateInstance(GetEntityType(eventProperties, web ?? listItem.Web, entityAssembly), new object[] { eventProperties, listItem, web });
        }

        /// <summary>
        /// Gets an entity for a listitem.
        /// </summary>
        /// <param name="listItem">The listitem that the new entity wraps</param>
        /// <param name="entityAssembly">The assembly where the entities are defined</param>
        /// <returns></returns>
        public static IEntity GetEntity(SPListItem listItem, Assembly entityAssembly)
        {
            return (IEntity)Activator.CreateInstance(GetEntityType(listItem.ContentTypeId, entityAssembly), new object[] { listItem });
        }

        /// <summary>
        /// Gets an entity for a listitem.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity</typeparam>
        /// <param name="listItem">The listitem that the new entity wraps</param>
        /// <returns></returns>
        public static TEntity GetEntity<TEntity>(SPListItem listItem)
        {
            return (TEntity)Activator.CreateInstance(typeof(TEntity), new object[] { listItem });
        }

        /// <summary>
        /// Gets an entity out of event properties.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity</typeparam>
        /// <param name="eventProperties">The eventproperties for which the entity is instantiated</param>
        /// <param name="web">The web. If null the web of the eventProperties is taken.</param>
        /// <returns></returns>
        public static TEntity GetEntity<TEntity>(SPItemEventProperties eventProperties, SPWeb web)
        {
            return (TEntity)Activator.CreateInstance(typeof(TEntity), new object[] { eventProperties, web ?? eventProperties.Web });
        }

        /// <summary>
        /// Gets a proxy entity, which gets loaded on demand.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity</typeparam>
        /// <param name="id">The id of the listitem</param>
        /// <param name="title">The title of the listitem. If empty, the entity gets loaded when <see cref="Entity.Title"/> is accessed.</param>
        /// <param name="list">The list of the listitem</param>
        /// <returns></returns>
        public static TEntity GetEntity<TEntity>(int id, string title, SPList list)
        {
            return (TEntity)Activator.CreateInstance(typeof(TEntity), new object[] { id, title, list });
        }

        /// <summary>
        /// Stores the entity types corresponding to the contenttypes
        /// </summary>
        private static Dictionary<Assembly, Dictionary<SPContentTypeId, Type>> _contentTypesToEntity;

        private static Type GetEntityType(SPItemEventProperties eventProperties, SPWeb web, Assembly entityAssembly)
        {
            SPContentTypeId contentTypeId = SPContentTypeId.Empty;
            //if (eventProperties.ListItem != null)
            //{
            //    if (eventProperties.ListItem.ContentTypeId == default(SPContentTypeId))
            //    {
            //        contentTypeId = eventProperties.ListItem.ContentType.Id;
            //    }
            //    else
            //    {
            //        contentTypeId = eventProperties.ListItem.ContentTypeId;
            //    }
            //    //SPListItem.ContentType.Id creates an unreleased SPRequest object
            //    //eventProperties.ListItem.Web.Dispose();  <= TODO: Review for SP2010!
            //}
            //else
            if (eventProperties.AfterProperties["ContentTypeId"] != null)
            {
                contentTypeId = new SPContentTypeId(eventProperties.AfterProperties["ContentTypeId"].ToString());
            }
            if (contentTypeId.IsParentOf(SPContentTypeId.Empty))
            {
                contentTypeId = web.Lists[eventProperties.ListId].ContentTypes[0].Id;
            }

            return GetEntityType(contentTypeId, entityAssembly);
        }

        /// <summary>
        /// Returns the entity type for a given content type.
        /// </summary>
        /// <remarks>
        /// With the first call this method loops through all classes of the <see cref="Assembly"/> and stores the entity classes
        /// indicated by the <see cref="ContentTypeAttribute"/> attribute.
        /// </remarks>
        /// <param name="guid">The contenttype id that matches an entity</param>
        /// <param name="Assembly">The assembly that holds the entity types.</param>
        /// <returns></returns>
        private static Type GetEntityType(SPContentTypeId guid, Assembly Assembly)
        {
            if (guid == SPContentTypeId.Empty) { return null; }
            if (_contentTypesToEntity == null || _contentTypesToEntity.Count == 0)
            {
                _contentTypesToEntity = new Dictionary<Assembly, Dictionary<SPContentTypeId, Type>>();
            }
            if (!_contentTypesToEntity.ContainsKey(Assembly))
            { // first call -> store the entity classes
                var contentTypesEntitiesMapping = new Dictionary<SPContentTypeId, Type>();
                foreach (var type in Assembly.GetTypes())
                {
                    type.GetCustomAttributes(typeof(ContentTypeAttribute), false).ToList()
                        .ForEach(ca => contentTypesEntitiesMapping.Add((ca as ContentTypeAttribute).Id, type));
                }
                // add default entities (like Item, Event, Contact, ...) from the Framework assembly
                foreach (var type in Assembly.GetAssembly(typeof(Entity)).GetTypes())
                {
                    type.GetCustomAttributes(typeof(ContentTypeAttribute), false).ToList()
                        .ForEach(ca => contentTypesEntitiesMapping.Add((ca as ContentTypeAttribute).Id, type));
                }
                _contentTypesToEntity.Add(Assembly, contentTypesEntitiesMapping);
            }
            // find matching entity type
            return _contentTypesToEntity[Assembly][guid.NearestParent(_contentTypesToEntity[Assembly].Keys)];
        }

        /// <summary>
        /// Tries to get the content type id from an entity type.
        /// </summary>
        /// <typeparam name="TEntity">The entity type to get the content type id from</typeparam>
        /// <returns></returns>
        /// <exception cref="NotImplementedException">Throws a NotImplementedException, if there is no <see cref="ContentTypeAttribute"/> decorating the type.</exception>
        public static SPContentTypeId GetContentTypeId<TEntity>()
            where TEntity : IEntity
        {
            var type = typeof(TEntity);
            var attribute = (ContentTypeAttribute)type.GetCustomAttributes(typeof(ContentTypeAttribute), false).FirstOrDefault();
            
            if (attribute == null)
            {
                throw new NotImplementedException("Attribute \"ContentTypeAttribute\" is missing");
            }

            return attribute.Id;
        }
    }
}
